﻿using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using GameSchoolApplication.Models;
using System;

namespace GameSchoolApplication.Repository
{

	public class GameRepository : IDisposable
	{
		private  GameSchoolWebContext _db = new GameSchoolWebContext();

		/// <summary>
		/// Adds a game
		/// </summary>
		/// <param name="game"></param>
		public void AddGame(Game game)
		{
			_db.Games.Add(game);
			_db.SaveChanges();
		}

		/// <summary>
		/// Updates a game
		/// </summary>
		/// <param name="game"></param>
		public void UpdateGame(Game game)
		{
			_db.Entry(game).State = EntityState.Modified;
			_db.SaveChanges();
		}

		/// <summary>
		/// Deletes a game
		/// </summary>
		/// <param name="game"></param>
		public void DeleteGame(Game game)
		{
			_db.Games.Remove(game);
			_db.SaveChanges();
		}

		/// <summary>
		/// Opens a game
		/// </summary>
		/// <param name="game"></param>
        public void OpenGame(Game game)
        {
            game.IsOpen = true;
            OpenFirstLevel(game);
            _db.Entry(game).State = EntityState.Modified;
            _db.SaveChanges();
        }

		/// <summary>
		/// Opens the first level
		/// </summary>
		/// <param name="game"></param>
        private void OpenFirstLevel(Game game)
        {
            var level = game.Levels.OrderBy(l => l.LevelId).FirstOrDefault();
            if (level != null)
            {
                OpenLevel(level);
            }
        }

		/// <summary>
		/// Opens a level that belongs to a game 
		/// </summary>
		/// <param name="level"></param>
        private void OpenLevel(Level level)
        {
            foreach (var enrollment in level.Game.Enrollments)
            {
                var levelEntry = new StudentLevelPoints { StudentId = enrollment.StudentId, LevelId = level.LevelId, StartedAt = DateTime.Now };
                _db.StudentLevelPoints.Add(levelEntry);
            }
        }

		/// <summary>
		/// Closes a level
		/// </summary>
		/// <param name="level"></param>
        public void CloseLevel(Level level)
        {
            level.Complete = true;
            var nextLevel = level.Game.Levels.OrderBy(l => l.LevelId).Where(l => l.LevelId > level.LevelId).FirstOrDefault();
            if (nextLevel != null)
            {
                OpenLevel(nextLevel);
            }
            else
            {
                level.Game.Complete = true;
                _db.Entry(level.Game).State = EntityState.Modified;
            }
            _db.Entry(level).State = EntityState.Modified;
            _db.SaveChanges();
        }

		/// <summary>
		/// Deletes all games
		/// </summary>
		/// <param name="game"></param>
		public void DeleteAllGames(IEnumerable<Game> game)
		{
			foreach (var gamesToDelete in game)
			{
				DeleteGame(gamesToDelete);
			}	
		}

		/// <summary>
		/// Returns all gams to a List
		/// </summary>
		/// <returns></returns>
		public IEnumerable<Game> GetAllGames()
		{
			return _db.Games.ToList();
		}

		/// <summary>
		/// Returns a game if it exists otherwise null
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Game GetGameById(int? id)
		{
			var item = _db.Games.Where(m => m.GameId == id).SingleOrDefault();
			return item;
		}

		/// <summary>
		/// Adds a annaouncement
		/// </summary>
		/// <param name="announcement"></param>
		public void AddAnnouncement(Announcement announcement)
		{
			_db.Announcements.Add(announcement);
			_db.SaveChanges();
		}

		/// <summary>
		/// Updates a annaouncement
		/// </summary>
		/// <param name="announcement"></param>
		public void UpdateAnnouncement(Announcement announcement)
		{
			_db.Entry(announcement).State = EntityState.Modified;
			_db.SaveChanges();
		}

		/// <summary>
		/// Deletes announcement
		/// </summary>
		/// <param name="announcement"></param>
		public void DeleteAnnouncement(Announcement announcement)
		{
			_db.Announcements.Remove(announcement);
			_db.SaveChanges();
		}

		/// <summary>
		/// Deletes all annaouncement
		/// </summary>
		/// <param name="announcements"></param>
		public void DeleteAllAnnouncement(IEnumerable<Announcement> announcements)
		{
			foreach(var announcementsToDelete in announcements)
			{
				DeleteAnnouncement(announcementsToDelete);
			}
		}

		/// <summary>
		/// Retunrs a list of annaouncement
		/// </summary>
		/// <returns></returns>
		public IEnumerable<Announcement> GetAllAnnouncement()
		{
			return _db.Announcements.ToList();
		}

		/// <summary>
		/// Gets a annaouncement
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Announcement GetAnnouncement(int? id)
		{
			var item = _db.Announcements.Where(m => m.Id == id).SingleOrDefault();
			return item;
		}

		/// <summary>
		/// Gets all notification
		/// </summary>
		/// <returns></returns>
		public IEnumerable<Notification> GetAllNotifications()
		{
			return _db.Notifications.ToList();
		}

		/// <summary>
		/// Returns a notification 
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Notification GetNotificationById(int? id)
		{
			return _db.Notifications.Where(m => m.Id == id).SingleOrDefault();
		}

		/// <summary>
		/// Adds a level to a game, if first level does not exist and gam is opne then open first level
		/// and save changes 
		/// </summary>
		/// <param name="level"></param>
		public void AddLevel(Level level)
		{
            bool firstLevel = _db.Levels.Where(l => l.GameId == level.GameId).Count() == 0;
            Game game = GetGameById(level.GameId);
			_db.Levels.Add(level);
            if (firstLevel && game.IsOpen)
            {
                OpenFirstLevel(game);	
            }
			_db.SaveChanges();
		}

		/// <summary>
		/// Updates level on changes
		/// </summary>
		/// <param name="level"></param>
		public void UpdateLevel(Level level)
		{
			_db.Entry(level).State = EntityState.Modified;
			_db.SaveChanges();
		}

		/// <summary>
		/// Deletes a level
		/// </summary>
		/// <param name="level"></param>
		public void DeleteLevel(Level level)
		{
			_db.Levels.Remove(level);
			_db.SaveChanges();
		}

		/// <summary>
		/// Return all levels to a List
		/// </summary>
		/// <returns></returns>
		public IEnumerable<Level> GetAllLevel()
		{
			return _db.Levels.ToList();
		}

		/// <summary>
		/// Gets all the games levels
		/// </summary>
		/// <param name="gameId"></param>
		/// <returns></returns>
        public IEnumerable<Level> GetGameLevels(int gameId)
        {
            return _db.Levels.Where(l => l.GameId == gameId);
        }

		/// <summary>
		/// Retunrs a single instance of a level 
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Level GetLevelById(int? id)
		{
			var item = _db.Levels.Where(m => m.LevelId == id).SingleOrDefault();
			return item;
		}

		/// <summary>
		/// Adds a cheat
		/// </summary>
		/// <param name="cheat"></param>
		/// <param name="cost"></param>
		public void AddCheat(Cheat cheat, int cost)
		{
			_db.Cheats.Add(cheat);
			_db.SaveChanges();
			cheat.Cost = (cost > 0) ? cost : 0;
			_db.SaveChanges();
		}

		/// <summary>
		/// Retunrs all cheats
		/// </summary>
		/// <returns></returns>
		public IEnumerable<Cheat> GetAllCheats()
		{
			return _db.Cheats.ToList();
		}

		/// <summary>
		/// Returns a cheat by id
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Cheat GetCheatById(int? id)
		{
			var cheatRequested = _db.Cheats.Where(m => m.CheatId == id).SingleOrDefault();
			return cheatRequested;
		}

		/// <summary>
		///	Updates a cheat
		/// </summary>
		/// <param name="cheat"></param>
		public void UpDateCheat(Cheat cheat)
		{
			_db.Entry(cheat).State = EntityState.Modified;
			_db.SaveChanges();
		}

		/// <summary>
		/// Adds a teacher to game 
		/// </summary>
		/// <param name="gameInstructor"></param>
		/// <param name="teacher"></param>
		/// <param name="game"></param>
		public void AddTeacherToGame(GameInstructor gameInstructor, Person teacher, Game game)
		{
			if (gameInstructor != null)
			{
				gameInstructor.TeacherId = teacher.Id;
				gameInstructor.GameId = game.GameId;
				gameInstructor.UserName = teacher.UserName;
				gameInstructor.Name = teacher.Name;
				gameInstructor.Points = 0;
                _db.GameInstructors.Add(gameInstructor);
                _db.SaveChanges();
			}
		}

		/// <summary>
		/// Removes a teacher from a game
		/// </summary>
		/// <param name="gameInstructor"></param>
        public void RemoveTeacherFromGame(GameInstructor gameInstructor)
        {
            _db.GameInstructors.Remove(gameInstructor);
            _db.SaveChanges();
        }

		/// <summary>
		/// Adds a student to a game
		/// </summary>
		/// <param name="studentPoints"></param>
		/// <param name="student"></param>
		/// <param name="game"></param>
		public void AddStudentToGame(StudentPoints studentPoints, Person student, Game game)
		{
			if (studentPoints != null)
			{
				studentPoints.StudentId = student.Id;
				studentPoints.GameId = game.GameId;
				studentPoints.UserName = student.UserName;
				studentPoints.Name = student.Name;
                _db.StudentPoints.Add(studentPoints);
                if (game.IsOpen)
                {
                    var level = game.Levels.OrderBy(l => l.LevelId).FirstOrDefault();
                    if (level != null)
                    {
                        var levelEntry = new StudentLevelPoints { StudentId = studentPoints.StudentId, LevelId = level.LevelId, StartedAt = DateTime.Now };
                        _db.StudentLevelPoints.Add(levelEntry);
                    }
                }
                _db.SaveChanges();
			}
		}

		/// <summary>
		/// Removes a student from a game
		/// </summary>
		/// <param name="enrollment"></param>
        public void RemoveStudentFromGame(StudentPoints enrollment)
        {
            _db.StudentPoints.Remove(enrollment);
            _db.SaveChanges();
        }

		/// <summary>
		/// Returns GameInstructor
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
        public GameInstructor GetGameInstructor(int id)
        {
            return _db.GameInstructors.Where(gi => gi.Id == id).SingleOrDefault();
        }

		/// <summary>
		/// Returns student points by id
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
        public StudentPoints GetEnrollment(int id)
        {
            return _db.StudentPoints.Where(sp => sp.EnrollmentId == id).SingleOrDefault();
        }
        
		/// <summary>
		/// Returns the list of games wich teacher belongs to
		/// </summary>
		/// <param name="teacher"></param>
		/// <returns></returns>
        public IEnumerable<Game> GetTeacherGames(Person teacher)
        {
            IEnumerable<GameInstructor> instructors = _db.GameInstructors.Where(gi => gi.TeacherId == teacher.Id);
            return _db.Games.Join(instructors, g => g.GameId, gi => gi.GameId, (g, gi) => g).ToList();
        }

		/// <summary>
		/// Returns all the student games
		/// </summary>
		/// <param name="student"></param>
		/// <returns></returns>
		public IEnumerable<Game> GetStudentGames(Person student)
		{
			IEnumerable<StudentPoints> studentsGame = _db.StudentPoints.Where(m => m.StudentId == student.Id);
			return _db.Games.Join(studentsGame, m => m.GameId, mi => mi.GameId, (m, mi) => m).ToList();
		}

		/// <summary>
		/// Returns all announcements of a game
		/// </summary>
		/// <param name="game"></param>
		/// <returns></returns>
		public IEnumerable<Announcement> AnnouncementsOfGame(Game game)
		{
			IEnumerable<Announcement> annaouncementGame = _db.Announcements.Where(m => m.GameId == game.GameId);
			return _db.Announcements.Join(annaouncementGame, m => m.GameId, mi => mi.GameId, (m, mi) => m).ToList();
		}
		
		/// <summary>
		/// Returns all notification of game
		/// </summary>
		/// <param name="game"></param>
		/// <returns></returns>
		public IEnumerable<Notification> NotificationOfGame(Game game)
		{
			IEnumerable<Notification> notificationOfGame = _db.Notifications.Where(m => m.GameId == game.GameId);
			return _db.Notifications.Join(notificationOfGame, m => m.GameId, mi => mi.GameId, (m, mi) => m).ToList();
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing) { }
	}
}